package com.gcssloop.color_picker;

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.LinearInterpolator;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 作用：拾色器
 * 作者：lql
 * 修订：GcsSloop
 * 摘要：提供拾色器功能。
 * 1. 自动生成调色盘，如果调色盘不存在，则会自动生成。
 * 2. 根据坐标转换为颜色，也可以根据颜色转换为实际坐标。
 */
public class ColorPicker extends View {
    //region 静态属性

    private static final String COLOR_PICKER_PNG = "ColorPikerImage%dx%d.png";  // 私有目录存储的调色板名字
    private static final String KEY_CURRENT_COLOR = "KEY_CURRENT_COLOR";  // 私有目录存储的调色板名字

    private static final float DEFAULT_SHADOW_RADIUS = 0;           // 默认阴影半径 dp
    private static final float DEFAULT_BORDER_WIDTH = 4;            // 默认边缘宽度 dp
    private static final int DEFAULT_BORDER_COLOR = 0xFFFFFFFF;     // 默认边缘颜色 color
    private static final float DEFAULT_THUMB_RADIUS = 20;           // 拖动按钮半径 dp
    private static final float DEFAULT_THUMB_WIDTH = 3;             // 拖动按钮边缘宽度 dp
    private static final int DEFAULT_TEXT_ID = R.string.loading;    // 默认的加载文本 id
    private static final float DEFAULT_TEXT_SIZE = 16;              // 默认加载文本大小 sp
    private static final int DEFAULT_TEXT_COLOR = 0xFF3C3B3F;       // 默认加载文本颜色 sp

    //endregion
    //region 可配置属性

    private float mShadowRadius;                    // 阴影半径 px
    private float mBorderWidth;                     // 边缘宽度 px
    private int mBorderColor;                       // 边缘颜色 color
    private float mThumbRadius;                     // 拖动按钮半径 px
    private float mThumbWidth;                      // 拖动按钮边缘宽度 px
    private String mLoadingText;                    // 加载调色板时显示的文本
    private float mLoadingTextSize;                 // 加载调色板时的文本大小 px
    private int mLoadingTextColor;                  // 加载调色板时的文本颜色 color
    private boolean hide = false;                   // 是否隐藏选中位置的按钮 bool

    //endregion
    //region 自动计算属性

    private float circleRadius;                     // 调色板半径
    private Rect contentRect;                       // 内容区域
    private Bitmap rgbBitmap;                       // 调色板图片
    private Point selectedPoint = new Point();      // 选中的位置
    private boolean mIsDragging = false;            // 是否正在拖动
    private int latest_x = 0;                       // 上一次选择位置 X
    private int latest_y = 0;                       // 上一次选择位置 Y

    private Paint mTextPaint;                       // 绘制文本用的画笔
    private Paint mBorderPaint;                     // 绘制边框和阴影用的画笔
    private Paint mThumbPaint;                      // 绘制拖动按钮的画笔

    //endregion
    //region 生命周期 ----------------------------------------------------------------------------------

    public ColorPicker(Context context) {
        this(context, null);
    }

    public ColorPicker(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ColorPicker(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        initAttrs(context, attrs);
        initPaint();
    }

    private void initAttrs(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ColorPicker);
        mShadowRadius = ta.getDimensionPixelSize(R.styleable.ColorPicker_cp_shadow_radius, (int) dp2px(DEFAULT_SHADOW_RADIUS));
        mBorderWidth = ta.getDimensionPixelSize(R.styleable.ColorPicker_cp_border_width, (int) dp2px(DEFAULT_BORDER_WIDTH));
        mBorderColor = ta.getColor(R.styleable.ColorPicker_cp_border_color, DEFAULT_BORDER_COLOR);
        mThumbRadius = ta.getDimensionPixelOffset(R.styleable.ColorPicker_cp_thumb_radius, (int) dp2px(DEFAULT_THUMB_RADIUS));
        mThumbWidth = ta.getDimensionPixelOffset(R.styleable.ColorPicker_cp_thumb_width, (int) dp2px(DEFAULT_THUMB_WIDTH));
        mLoadingText = ta.getString(R.styleable.ColorPicker_cp_loading_text);
        if (null == mLoadingText || mLoadingText.length() <= 0) {
            mLoadingText = getContext().getString(DEFAULT_TEXT_ID);
        }
        mLoadingTextSize = ta.getDimensionPixelSize(R.styleable.ColorPicker_cp_loading_text_size, (int) sp2px(DEFAULT_TEXT_SIZE));
        mLoadingTextColor = ta.getColor(R.styleable.ColorPicker_cp_loading_text_color, DEFAULT_TEXT_COLOR);
        initAnimateAttr(ta);
        ta.recycle();
    }

    private void initPaint() {
        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextSize(mLoadingTextSize);
        mTextPaint.setColor(mLoadingTextColor);
        mTextPaint.setTextAlign(Paint.Align.CENTER);

        mBorderPaint = new Paint();
        mBorderPaint.setAntiAlias(true);
        mBorderPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mBorderPaint.setColor(mBorderColor);
        mBorderPaint.setStrokeWidth(mBorderWidth * 2);

        mThumbPaint = new Paint();
        mThumbPaint.setAntiAlias(true);
        mThumbPaint.setStrokeWidth(mThumbWidth);
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        float pl = getPaddingLeft() + mShadowRadius + mBorderWidth;
        float pt = getPaddingTop() + mShadowRadius + mBorderWidth;
        float pr = getPaddingRight() + mShadowRadius + mBorderWidth;
        float pb = getPaddingBottom() + mShadowRadius + mBorderWidth;
        float contentWidth = getWidth() - pl - pr;
        float contentHeight = getHeight() - pt - pb;
        int startX, startY, edgeLength;
        if (contentWidth < contentHeight) {
            edgeLength = (int) contentWidth;
            startX = (int) pl;
            startY = (int) (pt + (contentHeight - contentWidth) / 2);
        } else {
            edgeLength = (int) contentHeight;
            startX = (int) (pl + (contentWidth - contentHeight) / 2);
            startY = (int) pt;
        }
        contentRect = new Rect(startX, startY, startX + edgeLength, startY + edgeLength);
        circleRadius = Math.min(contentRect.width(), contentRect.height()) / 2;
        if (contentRect.width() > 0 && contentRect.height() > 0) {
            getPickerBitmap();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // 如果调色板尚未生成，绘制 loading 文本
        if (rgbBitmap == null) {
            canvas.drawText(mLoadingText, contentRect.centerX(), contentRect.centerY() + mLoadingTextSize / 2, mTextPaint);
            return;
        }
        int currentColor = getCurrentColor();
        mBorderPaint.setShadowLayer(mShadowRadius, 0, 0, currentColor);
        canvas.drawCircle(contentRect.centerX(), contentRect.centerY(), circleRadius, mBorderPaint);
        canvas.drawBitmap(rgbBitmap, null, contentRect, null);
        if (!hide) {
            mThumbPaint.setColor(currentColor);
            mThumbPaint.setStyle(Paint.Style.FILL);
            canvas.drawCircle(selectedPoint.x, selectedPoint.y, mCurrentThumbRadius, mThumbPaint);
            if (mThumbWidth > 0) {
                mThumbPaint.setColor(Color.WHITE);
                mThumbPaint.setStyle(Paint.Style.STROKE);
                canvas.drawCircle(selectedPoint.x, selectedPoint.y, mCurrentThumbRadius, mThumbPaint);
            }
        }
    }

    //endregion
    //region 手势处理 ----------------------------------------------------------------------------------

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!isEnabled()) {
            return false;
        }
        int action = event.getActionMasked();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                startDragging(event);
                updateDragging(event);
                startThumbRadiusAnimator(true);
                break;
            case MotionEvent.ACTION_MOVE:
                if (isDragging()) {
                    updateDragging(event);
                } else {
                    startDragging(event);
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                stopDragging();
                startThumbRadiusAnimator(false);
                break;
        }
        invalidate();
        return true;
    }

    private void startDragging(MotionEvent ev) {
        mIsDragging = true;
    }

    private boolean isDragging() {
        return mIsDragging;
    }

    private void stopDragging() {
        mIsDragging = false;
        int color = getColorByPoint(latest_x, latest_y, colorHsv);
        if (listener != null) {
            listener.onColorSelected(color, color2RGBW(color), true);
        }
    }

    private void updateDragging(MotionEvent ev) {
        int x = (int) ev.getX();
        int y = (int) ev.getY();
        if (latest_x != x || latest_y != y) {
            latest_x = x;
            latest_y = y;
            int color = getColorByPoint(latest_x, latest_y, colorHsv);
            if (listener != null) {
                listener.onColorChanged(color, color2RGBW(color), true);
            }
        }
    }

    //endregion
    //region 动效处理 ----------------------------------------------------------------------------------

    private static final float DEFAULT_THUMB_GAIN = 1.5f;           // 默认点击放大倍数
    private static final int DEFAULT_THUMB_TOUCH_DURATIONN = 100;   // 默认点击动画时长

    private float mThumbTouchGain = 1.5f;   // 放大倍率
    private float mLastThumbRadius;
    private float mCurrentThumbRadius;
    private float mTargetThumbRadius;
    private int mThumbTouchDuration;
    private ValueAnimator mThumbRadiusAnimator;

    private void initAnimateAttr(TypedArray ta){
        mThumbTouchGain = ta.getFloat(R.styleable.ColorPicker_cp_thumb_touch_gain, DEFAULT_THUMB_GAIN);
        mThumbTouchDuration = ta.getInt(R.styleable.ColorPicker_cp_thumb_touch_duration, DEFAULT_THUMB_TOUCH_DURATIONN);
        mCurrentThumbRadius = mThumbRadius;
    }

    private void startThumbRadiusAnimator(boolean isDown) {
        if (isDown) {
            startThumbRadiusAnimator(mThumbRadius * mThumbTouchGain);
        } else {
            startThumbRadiusAnimator(mThumbRadius);
        }
    }


    private void startThumbRadiusAnimator(float targetThumbRadius) {
        stopThumbRadiusAnimator();
        mLastThumbRadius = mCurrentThumbRadius;
        mTargetThumbRadius = targetThumbRadius;

        mThumbRadiusAnimator = ObjectAnimator.ofFloat(mLastThumbRadius,mTargetThumbRadius);
        mThumbRadiusAnimator.setDuration(mThumbTouchDuration);
        mThumbRadiusAnimator.setInterpolator(new LinearInterpolator());
        mThumbRadiusAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurrentThumbRadius = (float) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        mThumbRadiusAnimator.start();
    }

    // 取消动画
    private void stopThumbRadiusAnimator() {
        if (null != mThumbRadiusAnimator) {
            mThumbRadiusAnimator.cancel();
            mThumbRadiusAnimator = null;
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        stopThumbRadiusAnimator();
    }

    //endregion
    //region 色彩处理 ----------------------------------------------------------------------------------

    // 当前的颜色
    private float[] colorHsv = {0f, 0f, 1f};

    private int getCurrentColor() {
        if (null == contentRect) {
            return Color.TRANSPARENT;
        }
        float hueInPiInterval = colorHsv[0] / 180f * (float) Math.PI;
        selectedPoint.x = contentRect.left + (int) (Math.cos(hueInPiInterval) * colorHsv[1] * circleRadius + circleRadius);
        selectedPoint.y = contentRect.top + (int) (-Math.sin(hueInPiInterval) * colorHsv[1] * circleRadius + circleRadius);

        return getColorByPoint(selectedPoint.x, selectedPoint.y, colorHsv);
    }

    // 根据位置获取颜色
    private int getColorByPoint(int x, int y, float[] hsv) {
        x -= ((contentRect.left + contentRect.right) / 2);
        y -= ((contentRect.top + contentRect.bottom) / 2);
        double centerDist = Math.sqrt(x * x + y * y);
        hsv[0] = (float) (Math.atan2(y, -x) / Math.PI * 180f) + 180;
        hsv[1] = Math.max(0f, Math.min(1f, (float) (centerDist / circleRadius)));
        hsv[2] = 1f;
        return Color.HSVToColor(hsv);
    }

    //endregion
    //region 图像处理 ----------------------------------------------------------------------------------

    // 获取调色板图片，如果图片不存在则生成
    private Bitmap getPickerBitmap() {
        final String fileName = String.format(COLOR_PICKER_PNG, contentRect.width(), contentRect.height());
        rgbBitmap = getBitmap(getContext(), fileName);
        if (rgbBitmap == null) {
            new Thread() {
                public void run() {
                    try {
                        rgbBitmap = createColorPNG(contentRect);
                        saveImagePNG(getContext(), fileName, rgbBitmap);
                        post(new Runnable() {
                            @Override
                            public void run() {
                                invalidate();
                            }
                        });
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }
        return rgbBitmap;
    }

    // 生成调色板图片
    private static Bitmap createColorPNG(Rect contentRect) {
        Bitmap rgbBitmap = Bitmap.createBitmap(contentRect.width(), contentRect.height(), Bitmap.Config.ARGB_8888);
        int[] pixels = new int[contentRect.width() * contentRect.height()];
        float[] hsv = new float[]{0f, 0f, 1f};
        int w = contentRect.width();
        int h = contentRect.height();
        int circleRadius = Math.min(contentRect.width(), contentRect.height()) / 2;
        int x = circleRadius, y = -circleRadius;
        for (int i = 0; i < pixels.length; i++) {
            if (i % w == 0) {
                x = circleRadius;
                y++;
            } else {
                x--;
            }

            double centerDist = Math.sqrt(x * x + y * y);
            if (centerDist <= circleRadius) {
                hsv[0] = (float) (Math.atan2(y, x) / Math.PI * 180f) + 180;
                hsv[1] = (float) (centerDist / circleRadius);
                pixels[i] = Color.HSVToColor(hsv);
            } else {
                pixels[i] = 0x00000000;
            }
        }
        rgbBitmap.setPixels(pixels, 0, w, 0, 0, w, h);
        return rgbBitmap;
    }

    // 获取私有目录下的图片
    private Bitmap getBitmap(Context context, String fileName) {
        FileInputStream fis = null;
        Bitmap bitmap = null;
        try {
            fis = context.openFileInput(fileName);
            bitmap = BitmapFactory.decodeStream(fis);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (OutOfMemoryError e) {
            e.printStackTrace();
        } finally {
            try {
                fis.close();
            } catch (Exception e) {
            }
        }
        return bitmap;
    }

    // 保存图片
    private void saveImagePNG(Context context, String fileName, Bitmap bitmap)
            throws IOException {
        saveImage(context, fileName, bitmap, 100, Bitmap.CompressFormat.PNG);
    }

    // 保存图片
    private void saveImage(Context context, String fileName,
                           Bitmap bitmap, int quality, Bitmap.CompressFormat format) throws IOException {
        if (bitmap == null || fileName == null || context == null)
            return;

        FileOutputStream fos = context.openFileOutput(fileName,
                Context.MODE_PRIVATE);
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        bitmap.compress(format, quality, stream);
        byte[] bytes = stream.toByteArray();
        fos.write(bytes);
        fos.close();
    }

    //endregion
    //region 状态存储 ----------------------------------------------------------------------------------


    @Override
    protected Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putParcelable("superState", super.onSaveInstanceState());
        bundle.putInt(KEY_CURRENT_COLOR, getCurrentColor());
        return bundle;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            int color = bundle.getInt(KEY_CURRENT_COLOR);
            setColor(color);
            state = bundle.getParcelable("superState");
        }
        super.onRestoreInstanceState(state);
    }

    private int[] color2RGBW(int color) {
        int[] rgbw = new int[4];
        rgbw[0] = (color & 0xff0000) >> 16;
        rgbw[1] = (color & 0xff00) >> 8;
        rgbw[2] = (color & 0xff);
        return rgbw;
    }

    //endregion
    //region 基础工具 ----------------------------------------------------------------------------------

    private float sp2px(float spValue) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, getResources().getDisplayMetrics());
    }

    private float dp2px(float dpValue) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics());
    }

    //endregion
    //region 公开方法 ---------------------------------------------------------------------------------

    /**
     * 设置颜色
     *
     * @param color Color
     */
    public void setColor(int color) {
        Color.colorToHSV(color, colorHsv);
        if (null != listener) {
            listener.onColorSelected(color, color2RGBW(color), false);
        }
        invalidate();
    }

    /**
     * 设置颜色
     *
     * @param rgbw [R, G, B, W]
     */
    public void setColor(int[] rgbw) {
        Color.RGBToHSV(rgbw[0], rgbw[1], rgbw[2], colorHsv);
        if (null != listener) {
            listener.onColorSelected(getCurrentColor(), rgbw, false);
        }
        invalidate();
    }

    /**
     * 是否隐藏拖动按钮
     *
     * @param isHide true 表示隐藏
     */
    public void setHide(boolean isHide) {
        hide = isHide;
        invalidate();
    }

    //endregion
    //region 回调接口 ----------------------------------------------------------------------------------

    private OnColorSelectedListener listener;

    public void setColorChangedListener(OnColorSelectedListener colorChangedListener) {
        this.listener = colorChangedListener;
    }

    public interface OnColorSelectedListener {
        void onColorChanged(int color, int[] rgbw, boolean isTouch);

        void onColorSelected(int color, int[] rgbw, boolean isTouch);
    }

    //endregion
}
